/**
 *  Copyright (c) Filipe Manana <fdmanana@gmail.com>
 *  All rights reserved.
 * 
 *  This file is part of jsdet.
 *
 *  jsdet is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  jsdet is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with jsdet.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jsdet.parsing.reporting;

import java.util.*;

import jsdet.parsing.GlobalParsingStats;
import jsdet.parsing.enums.ClassMemberType;


public class ConsoleReporter extends AbstractReporter {

    private int memberDisplayWidth = 1;
    private int closureMemberDisplayWidth = 1;


    @Override
    public void reportGlobalParsingStats(GlobalParsingStats stats) {

        classesAlreadyReported.clear();

        System.out.println();

        if ( areFunctionsReported() ) {
            System.out.println(stats.getFunctions().size() + " functions found.");
        }

        if ( areClosureObjectsReported() ) {
            System.out.println(stats.getClosureObjects().size() + " closure objects found.");
        }

        System.out.println(stats.getClasses().size() + " classes found.");
        System.out.println();

        if ( areFunctionsReported() ) {
            reportFunctions(stats);
            System.out.println();
        }

        if ( areClosureObjectsReported() ) {
            findClosureMembersDisplayMaxWidth(stats);
            reportClosureObjects(stats);
            System.out.println();
        }

        findMembersDisplayMaxWidth(stats);
        reportClasses(stats);
    }


    private void reportClasses(GlobalParsingStats stats) {

        Set<String> classes = null;

        switch ( classesSortOrder ) {
            case ASC:
                classes = stats.getClassesOrderedByHierarchyLevelAsc();
                break;

            case DESC:
                classes = stats.getClassesOrderedByHierarchyLevelDesc();
                break;

            case DEFAULT:
            default:
                classes = stats.getClasses();
        }

        for ( String className : classes ) {

            if ( classesAlreadyReported.contains(className) ) {
                continue;
            }

            if ( !isClassInFilter(className) ) {
                continue;
            }

            if ( classesSortOrder == ClassesSortOrder.DEFAULT ) {
                List<String> upperClasses = stats.getClassBaseHierarchy(className);

                for ( int i = upperClasses.size() - 1; i >= 0; i-- ) {
                    String upperClass = upperClasses.get(i);

                    if ( !stats.hasClass(upperClass) ) {
                        continue;
                    }

                    if ( classesAlreadyReported.contains(upperClass) ) {
                        continue;
                    }

                    if ( !isClassInFilter(upperClass) ) {
                        continue;
                    }

                    reportClass(upperClass, stats);
                    classesAlreadyReported.add(upperClass);
                }
            }

            reportClass(className, stats);
            classesAlreadyReported.add(className);
        }
    }


    private void reportClass(String className, GlobalParsingStats stats) {
        reportClassBasicInfo(className, stats);

        if ( reportNamesOnly ) {
            return;
        }

        reportOwnMembersCount(className, stats);
        reportOverridedMembersCount(className, stats);

        if ( areInheritedMembersReported() ) {
            reportInheritedMembersCount(className, stats);
        }

        System.out.println();
        reportNonFunctionMembers(className, stats);
        reportFunctionMembers(className, stats);
        System.out.println();
        System.out.println();
    }


    private void reportInheritedMembersCount(String className, GlobalParsingStats stats) {
        Set<String> members = stats.getClassInheritedMembers(className);
        int inherited = members.size();

        if ( inherited > 0 ) {
            System.out.print("    ");
            System.out.println(inherited + " inherited members");
        }
    }


    private void reportOverridedMembersCount(String className, GlobalParsingStats stats) {
        Set<String> members = stats.getClassOverridingMembers(className);
        int overrided = members.size();

        if ( overrided > 0 ) {
            System.out.print("    ");
            System.out.println(overrided + " overriding members");
        }
    }


    private void reportOwnMembersCount(String className, GlobalParsingStats stats) {
        int owned = stats.getClassUniqueMembers(className).size();

        if ( owned > 0 ) {
            System.out.print("    ");
            System.out.println(owned + " own members");
        }
    }


    private void reportClassBasicInfo(String className, GlobalParsingStats stats) {
        List<String> upperClasses = stats.getClassBaseHierarchy(className);

        StringBuffer line = new StringBuffer("class  ");
        line.append(className);

        for ( String upperClass : upperClasses ) {
            line.append(" < ");
            line.append(upperClass);
        }

        System.out.println(line.toString());
    }


    private void reportFunctionMembers(String className, GlobalParsingStats stats) {
        reportMembersOfType(className, ClassMemberType.FUNCTION, stats);
    }


    private void reportNonFunctionMembers(String className, GlobalParsingStats stats) {
        reportMembersOfType(className, ClassMemberType.NON_FUNCTION, stats);
    }


    private void reportMembersOfType(String className, ClassMemberType memberType, GlobalParsingStats stats) {

        reportMembers(stats.getClassUniqueMembers(className, memberType), className, memberType.getLabel(), stats);
        reportMembers(stats.getClassOverridingMembers(className, memberType), className, memberType.getLabel(), stats);

        if ( areInheritedMembersReported() ) {
            reportMembers(stats.getClassInheritedMembers(className, memberType), className, memberType.getLabel(), stats);
        }
    }


    private void reportMembers(Set<String> members, String className, String memberTypeLabel, GlobalParsingStats stats) {

        for ( String member : members ) {
            String upperClass = stats.findFirstUpperClassWithMember(className, member);
            boolean overriding = stats.isClassMemberOverriding(className, member);
            boolean inherited = stats.classHasInheritedMember(className, member);
            StringBuffer line1Part2 = new StringBuffer("  | type: ");
            StringBuffer line2Part2 = new StringBuffer("");

            line1Part2.append(String.format("%-10s", memberTypeLabel));

            if ( inherited && (upperClass != null)) {
                line2Part2.append("  | inherited from: ");
                line2Part2.append(upperClass);
            } else if ( overriding && (upperClass != null)) {
                line2Part2.append("  | overrides: ");
                line2Part2.append(upperClass);
                line2Part2.append(".");
                line2Part2.append(member);
            }

            String filesLine = "";
            if ( areFileNamesReported() ) {
                Set<String> files = null;

                if ( inherited ) {
                    files = stats.getFilesWhereClassMemberIsDefined(upperClass, member);
                }
                else {
                    files = stats.getFilesWhereClassMemberIsDefined(className, member);
                }

                for ( Iterator<String> it = files.iterator(); it.hasNext(); ) {
                    String file = it.next();
                    filesLine += "\"" + file + "\"";

                    if ( it.hasNext() ) {
                        filesLine += ", ";
                    }
                }
            }

            System.out.print("    ");
            System.out.printf("%-" + memberDisplayWidth + "s", member);
            System.out.println(line1Part2.toString());

            String line2Part2String = line2Part2.toString();

            if ( !line2Part2String.isEmpty() ) {
                System.out.print("    ");
                System.out.printf("%-" + memberDisplayWidth + "s", "");
                System.out.println(line2Part2String);
            }

            if ( !filesLine.isEmpty() ) {
                System.out.print("    ");
                System.out.printf("%-" + memberDisplayWidth + "s", " ");
                System.out.println("  | files: " + filesLine);
            }

            if ( !filesLine.isEmpty() || !line2Part2String.isEmpty() ) {
                System.out.println();
            }
        }
    }


    private void reportFunctions(GlobalParsingStats stats) {

        Set<String> functions = stats.getFunctions();
        int functionDisplayWidth = getLargestStringWidth(functions);

        for ( String function : functions ) {
            System.out.printf("function  %-" + functionDisplayWidth + "s", function);

            if ( areFileNamesReported() ) {
                String file = stats.getFunctionFile(function);
                System.out.print("  | file: \"" + file + "\"");
            }

            System.out.println();
        }
    }


    private void reportClosureObjects(GlobalParsingStats stats) {

        Set<String> closureObjects = stats.getClosureObjects();
        int nameDisplayWidth = getLargestStringWidth(closureObjects);

        for ( String closureObject : closureObjects ) {
            Set<String> unknownMembers = stats.getClosureObjectMembers(closureObject, ClassMemberType.NON_FUNCTION);
            Set<String> functionMembers = stats.getClosureObjectMembers(closureObject, ClassMemberType.FUNCTION);

            System.out.printf("closure_object  %-" + nameDisplayWidth + "s", closureObject);

            if ( areFileNamesReported() ) {
                String file = stats.getClosureObjectFile(closureObject);
                System.out.print("  | file: \"" + file + "\"");
            }

            System.out.println();

            if ( reportNamesOnly ) {
                continue;
            }

            if ( !unknownMembers.isEmpty() ) {
                System.out.print("    ");
                System.out.println(unknownMembers.size() + " " + ClassMemberType.NON_FUNCTION.getLabel() + " members");
            }

            if ( !functionMembers.isEmpty() ) {
                System.out.print("    ");
                System.out.println(functionMembers.size() + " " + ClassMemberType.FUNCTION.getLabel() + " members");
            }

            System.out.println();

            for ( String member : unknownMembers ) {
                System.out.print("    ");
                System.out.printf("%-" + closureMemberDisplayWidth + "s", member);
                System.out.println("  | type: " + ClassMemberType.NON_FUNCTION.getLabel());
            }

            for ( String member : functionMembers ) {
                System.out.print("    ");
                System.out.printf("%-" + closureMemberDisplayWidth + "s", member);
                System.out.println("  | type: " + ClassMemberType.FUNCTION.getLabel());
            }

            System.out.println();
            System.out.println();
        }
    }


    private void findMembersDisplayMaxWidth(GlobalParsingStats stats) {
        Set<String> classes = stats.getClasses();
        Set<String> allMembers = new HashSet<String>();

        for ( String className : classes ) {

            if ( !isClassInFilter(className) ) {
                continue;
            }

            allMembers.addAll(stats.getClassMembers(className));
        }

        memberDisplayWidth = getLargestStringWidth(allMembers);
    }


    private void findClosureMembersDisplayMaxWidth(GlobalParsingStats stats) {
        Set<String> closureObjects = stats.getClosureObjects();
        Set<String> allClosuresMembers = new HashSet<String>();

        for ( String closureObject : closureObjects ) {
            allClosuresMembers.addAll(stats.getClosureObjectMembers(closureObject));
        }

        closureMemberDisplayWidth = getLargestStringWidth(allClosuresMembers);
    }


    private int getLargestStringWidth(Collection<String> list) {
        int max = 1;
        int w = 0;

        for ( String e : list ) {
            w = e.length();

            if ( w > max ) {
                max = w;
            }
        }

        return max;
    }
}
